C语言的弱项:错误的检测和处理并不是c语言的强项
- C语言对运行时错误以多种形式表示,而没有提供一种统一的方式
- 程序员必须将检测错误的代码编写在程序代码中,因此很容易忽略一些错误
扩展:C++语言对C语言的这一弱点进行了改进,提供了一种新的错误错误的方式-异常处理(exception handling)。
24.1 assert.h
:诊断
assert宏(函数)
断言:一个我们认为在正常情况下一顶为真的表达式。
错误信息:标准C要求在显示的消息中指明以下内容
- 传递给assert函数的参数
- 包括assert调用的文件名
- assert调用所在的行号
说明:检查断言,如果值不为0,会向
stderr
输出一条信息,并调用abort函数
终止程序。
技巧:因为引入了额外的检查,因此会增加程序的运行时间。可以在测试没问题后通过NDEBUG
(宏)进制assert
调用。
注意:因为assert可能会被禁用,因此不要在assert调用中使用有副作用的表达式。
原型assert.h
1 | /** |
1 | a[i]; |
禁止assert调用
1 |
24.2 errno.h
:错误
说明:除了
EDOM
和ERANGE
,还定义了其他宏,这是合法的,但命名要遵循C标准,即E数组或大写字母
errno宏
描述:如果确实是宏,C语言标准要求它表示左值4.2.2,以便和变量一样使用。
说明:如果确实是宏,C语言标准要求它表示左值4.2.2,以便和普通变量一样使用。
用途:函数被调用后会会为errno
赋值,如果errno
不为0,代表函数调用过程中有错误发生。
应用:大部分使用errno
变量的函数集中在math.h
,也有一些在标准库的其他部分。
相关宏:EDOM
和ERANGE
,errno中存储的值通常是这两个宏
宏(errno的值) | 错误类型 | 说明 |
---|---|---|
EDOM | 定义域错误 | 传递给函数的一个参数不属于函数的定义域 |
ERANGE | 取值范围错误 | 函数的返回值太大,无法用double |
1 | errno = 0; |
perror函数
描述:向
stderr
输出一条错误信息。
错误信息:sqrt error: Math argument
(定义域错误)
perror的参数 | 分号 | 空格 | 出错消息 | 换行符 |
---|---|---|---|---|
sqrt error | : | Math argument |
原型:
stdio.h
1 | /** |
1 | errno = 0; |
strerror函数
描述:根据错误类型行值返回指向错误字符串的指针。
原型:string.h
1 | /** |
24.3 signal.h
:信号处理
信号(signal):处理异常情况的工具
运行时错误
例如:除以0
程序以外导致的事件
例如:许多操作系统都允许用户终端或终止运行的程序
异步的:它们可以在程序执行过程中的任意时刻发生,而不仅是在程序员所知道的特定时刻发生
24.3.1 信号宏
兼容性:C标准不要求下面列表中的信号都会发生,大多数C语言的实现都至少支持其中一部分。
宏名 | 含义 |
---|---|
SIGABRT | 异常终止(可能由于调用abort导致) |
SIGFPE | 在数学运算中发生错误(可能是除以0或溢出) |
SIGILL | 非法指令 |
SIGINT | 中断 |
SIGSEGV | 非法存储访问 |
SIGTERM | 终止请求 |
24.3.2 signal函数
描述:为指定信号注册指定处理函数。
相关宏:SIG_ERR
说明:当注册失败时会返回该值
用途:检测注册处理函数是否成功特点
多对一:
信号
与处理函数是多对一
的关系
- 可以对多种信号绑定同一个处理函数,处理函数可以根据传入的参数(信号类型)决定进行哪种操作
- 也可以对同一个信号注册多个处理程序,但前面注册的会被后面注册的处理函数覆盖。
同步性:发出信号的行为是异步的,但处理函数处理的过程是同步的。也就是说,注册了处理函数的信号出现后,程序会暂停并执行信号处理函数,返回后暂停的程序从信号发生点恢复并继续执行。
信号 | 处理函数返回后程序行为 |
---|---|
SIGABRT | 终止 |
SIGFPE | 处理函数返回后的程序行为未定义 |
其它 | 暂停的程序从信号发生点恢复并继续执行 |
一次性:信号处理完之后,除非处理函数被重新注册,否则该信号不回被同一个函数处理两遍。
无限递归问题:如果信号是由处理这个信号的函数引发的,如果没有其它机制将会发生无限递归。所以,C语言要求,除了SIGTLL
,当一个信号的处理函数被调用时,该信号对应的处理函数要么要被重置为SIG_DFL
或以其它方式加以封锁。限制:处理函数和普通函数相比多了一些限制
可以 | 不可以 |
---|---|
可以忽略该信号 | 自由调用库函数 |
执行一些错误修复 | 访问静态存储期限的变量 |
终止程序 | |
可以调用signal ,只要第一个参数为正被处理的信号 |
|
调用库函数,只要信号处理函数是由raise或abort调用的 |
原型:
signal.h
1 | /** |
1 | // 声明函数指针 |
24.3.3 预定义的信号处理函数
说明:出了编写我们自己的信号处理函数,我们还可以选择使用
signal.h
提供的预定义的处理函数。
预定义的信号处理函数命名规则:
SIG_
大写字母
SIG_DFL宏(函数)
说明:按“默认”的方式处理
描述:行为由实现定义,大多数情况下会导致程序终止。
原型:signal.h
1 | /** |
1 | signal(SIGINT, SIG_IGN); // 使用默认行为响应“中断”信号 |
SIG_IGN宏(函数)
描述:什么都不做,忽略信号
原型:signal.h
1 | /** |
24.3.4 raise函数
说明:触发信号
原型:signal.h
1 | /** |
1 | // 触发SIGABORT |
24.3.5 程序:测试信号
1 | /** |
1 | $ ./tsignal |
24.4 setjmp.h:非局部跳转
说明:通常情况下,函数调用后会回到它被调用的位置。但
setjmp.h
提供了使一个函数直接跳转到另一个函数(而且不需要返回)的方式。
goto:只能配合标记实现局部跳转
,也就是在同一个函数内部跳转。
setjmp宏(函数)
描述:标记程序中的一个“位置”
应用:生成标记位置,稍后提供给longjmp函数
限制:按照标准C
,只有两种使用setjmp的方式是合法的(否则不具备可移植性)
- 作为表达式语句(可能会前置转换成void)
- 作为
if、switch、while、do、for
语句中控制表达式的一部分(constexp
:计算结果为整数的常量表达式;op
:关系或判等运算符)
- setjmp(…)
- !setjmp(…)
constexp
op
constexp
- setjmp(…)
op
constexp
原型:
setjmp.h
1 | /** |
longjmp函数
描述:首先根据参数env的值恢复当前环境,然后从
setjmp宏
调用中返回
注意:一定要确保参数env已经被setjmp宏
初始化了,否则程序可能会崩溃。
应用:可以由多种潜在的用途,但主要被用于错误处理。
原型:setjmp.h
1 | /** |
程序:测试setjmp和longjmp
1 | /** |
1 | $ ./tsetjmp |